package util;

/**
 * TimeBlock.java
 * Purpose: a block of time in which a class is scheduled
 * 
 * @author Hans Brems, Wim Vandebriel
 * @version 2.0 09/11/2011
 */

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

import util.enums.CourseDay;

public class TimeBlock {

	private final int MINUTESPERDAY = 1440;
	
	private CourseDay day;
	private int duration;
	private int hour;
	private int minute;
	
	/** Constructs a new time block */
	public TimeBlock() {
	}

	/** Constructs a new time block */
	public TimeBlock(CourseDay day, String startTime, int duration) throws Exception {
		this.day = day;
		this.setStartTime(startTime);
		this.setDuration(duration);
	}

	/** Returns the day */
	public CourseDay getDay() {
		return this.day;
	}
	
	/** Sets the day */
	public void setDay(CourseDay day) {
		this.day = day;
	}
	
	/** Returns the duration */
	public int getDuration() {
		return this.duration;
	}
	
	/** Sets the duration */
	public void setDuration(int duration) throws Exception {
		if (this.getStartTime() + duration > MINUTESPERDAY) {
			throw new Exception("Duration exceeds day");
		} else {
			this.duration = duration;
		}
	}
	
	/** Returns the hour */
	public int getHour() {
		return this.hour;
	}
	
	/** Returns the minute */
	public int getMinute() {
		return this.minute;
	}
	
	/** Sets the start time */
	public void setStartTime(String startTime) throws Exception {
		
		int hour;
		int minute;
		String[] values = startTime.split(":");
		
		// Test format
		if (values.length != 2) {
			throw new Exception("Invalid time format (correct: hh:mm)");
		}
		
		// Extract time variables
		hour = Integer.parseInt(values[0]);
		minute = Integer.parseInt(values[1]);
		
		// Test hour
		if (hour < 0 || hour > 23) {
			throw new Exception("Invalid hour value");
		}
		
		// Test minute
		if (minute < 0 || minute > 59) {
			throw new Exception("Invalid minute value");
		}
		
		// Set time
		this.hour = hour;
		this.minute = minute;
		this.duration = 0;
	}

	/** Checks if this time block overlaps with another one */
	public boolean overlaps(TimeBlock timeBlock) {
		
		int dur = 0;
		int first = 0;
		int last = 0;
		
		// Find out which time block is scheduled first
		switch (this.compareTo(timeBlock)) {
			case 0:
				return true;
			case -1:
				dur = this.duration;
				first = this.getStartTime();
				last = timeBlock.getStartTime();
				break;
			case 1:
				dur = timeBlock.getDuration();
				first = timeBlock.getStartTime();
				last = this.getStartTime();
				break;
		}
		
		// Check if the first block's duration overlaps with the last block
		if ((first + dur) >= last) {
			return true;
		}
		
		return false;
	}

	/** Returns a string representation of the start time */
	public String printStartTime() {
		
		// Create calendar to hold time
		Calendar gc = GregorianCalendar.getInstance();
		gc.set(Calendar.HOUR_OF_DAY, this.hour);
		gc.set(Calendar.MINUTE, this.minute);
		
		// Format time
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
		String startTime = sdf.format(gc.getTime());
		
		return String.format("%s", startTime);
	}
	
	/** Returns a string representation of the stop time */
	public String printStopTime () {
		
		// Calculate stop time
		int time = this.getStopTime();
		int hour = time / 60;
		int minute = time % 60;
		
		// Create calendar to hold time
		Calendar gc = GregorianCalendar.getInstance();
		gc.set(Calendar.HOUR_OF_DAY, hour);
		gc.set(Calendar.MINUTE, minute);
		
		// Format time
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
		String stopTime = sdf.format(gc.getTime());
		
		return String.format("%s", stopTime);
	}
	
	/** Returns a tab formatted string */
	public String getFormattedString(){
		String s = "%s\t%d\t%d\t%d";
		return String.format(s, this.day.toString(), this.hour, this.minute, this.duration);
	}
	
	/** Returns a string representation of the object */
	@Override
	public String toString() {
		
		// Create calendar to hold time
		Calendar gc = GregorianCalendar.getInstance();
		gc.set(Calendar.HOUR_OF_DAY, this.hour);
		gc.set(Calendar.MINUTE, this.minute);
		
		// Format time
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
		String startTime = sdf.format(gc.getTime());
		
		return String.format("%s %s", this.day, startTime);
	}
	
	/** Checks for equality with another object */
	@Override
	public boolean equals(Object object) {

		TimeBlock otherTimeBlock;
		
		// Check for same reference
		if (this == object) {
			return true;
		}
		
		// Check if object type is equal
		if (object == null || object.getClass() != this.getClass()) {
			return false;
		}
		
		// Cast object to correct class
		otherTimeBlock = (TimeBlock)object;
			
		// Check if object properties are equal
		return this.getStartTime() == otherTimeBlock.getStartTime() &&
				this.day == otherTimeBlock.getDay();
	}

	/**
	 * Compares two TimeBlock objects
	 * @param timeBlock a TimeBlock object
	 * @return 0 -> start times are equal
	 * 	-1 -> this start time precedes the start time of the argument argument
	 * 	+1 -> this start time follows the start time of the argument
	 *         start time represented by the argument.
	 */
	public int compareTo(TimeBlock timeBlock) {
		
		int result = 0;

		// Check if blocks are within same day
		if (this.day == timeBlock.getDay()) {
		
			// This time block's start time precedes the argument
			if (this.getStartTime() < timeBlock.getStartTime()) {
				result = -1;
			}
			
			// This time block's start time follows the argument
			if (this.getStartTime() > timeBlock.getStartTime()) {
				result = 1;
			}
		} else {
			
			// Compare days
			result = this.day.compareTo(timeBlock.getDay());
		}
	
		return result;
	}
	
	/* Returns an integer value indicating the start time (minutes from 0 hour) */
	private int getStartTime() {
		return (this.hour * 60) + this.minute;
	}
	
	/* Returns an integer value indicating the stop time (minutes from 0 hour) */
	private int getStopTime() {
		return this.getStartTime() + this.duration;
	}
}